`define JITTER_TIME 0.1
`define PERMIT_TIME 0.2

module cru_mon#(
    parameter NAME     = "CLK",
    parameter FREQ0    = 1200 ,
    parameter FREQ1    = 1000 ,
    parameter FREQ2    = 24   ,
    parameter FREQ3    = 1000 ,
    parameter GDLY_CHK = 5'd12,
    parameter RDLY_CHK = 5'd20,
    parameter SDLY_CHK = 8'd100) (
input           clk         ,
input           lck         ,//default:1
input           gate_reg    ,//default:0,means has clock 
input           rstn        ,//default:1
input           rst_reg     ,//default:0,means no rst
input [ 1: 0]   sel_reg     ,// 
input [15: 0]   div_reg      // 
);
bit print_en = 1'b1 ;
bit lost = 0        ;

function real abs (input var real i);
  reg [63:0] tmp;
  begin
    tmp = $realtobits(i);
    tmp[63] = 0;
    abs = $bitstoreal(tmp);
    //$display("abs %f,%f",i,abs);
  end
endfunction

//calculate the period
real current_period = 0.1   ;
real last_period = 0.1      ;
real last_stamp = 0        ;
bit clk_div2_duty50 = 1'b0 ;//solved this case:  the icg clk's duty is to low, mon clk can not sample the right level. 
always @(posedge clk) begin
  current_period  <= $realtime - last_stamp ;
  last_period     <= current_period         ; 
  last_stamp      <= $realtime              ;
  clk_div2_duty50 <=~clk_div2_duty50        ;
end

//generate the mon clk
bit pos_clk1st  = 'b0; bit pos_clk2nd  = 'b0;
always @(posedge clk) begin
  pos_clk1st <= 'b1;
  pos_clk2nd <= pos_clk1st; 
end

real mon_period = 10     ;//if very large , the mon_clk have no generated,because it is too slow freq. 
bit clk_mon=0;
initial begin
  clk_mon = 0;
  while(1)begin
    if(pos_clk2nd)begin
      mon_period <= (abs(current_period-last_period)<0.1) ? current_period : mon_period;//filter the jitter period
      if(mon_period <= 0.1) begin
        $display("\033[1;31m MON PERIOD IS TOO SMALL %7.6f",$realtime," ns MON_PERIOD(%-8f",mon_period,")\033[0m");//red
        $stop;
      end
    end
    #(mon_period/3) clk_mon = ~clk_mon;//div=3 so no same phase with the clk_div2_duty50
  end
end

//generate lost information
bit [7:0] shift_level = 'b0 ;
always @(posedge clk_mon) begin
  shift_level <= {shift_level[6:0],clk_div2_duty50  };
  if((shift_level == 'h0) || (&shift_level) || (!lck))begin
    lost <= 'b1;
  end
  else begin
    lost <= 'b0;
  end
end

//generate freq information
wire    freq_keep = abs(current_period - last_period) <= `JITTER_TIME ;
real    last_freq ; assign    last_freq =(    last_period == 0 ) ? 0 : 1000/last_period    ;
real current_freq ; assign current_freq =( current_period == 0 ) ? 0 : 1000/current_period ;
bit[15:0] change_cnt= 0   ;

//print
initial begin
  while(print_en)begin
    wait( lost); $display("\033[1;34m CRU_MON: (%7.6f",$realtime," ns) CLK(%-60s",NAME,") LOST  \033[0m");//BLUE
    wait(~lost); $display("\033[1;32m CRU_MON: (%7.6f",$realtime," ns) CLK(%-60s",NAME,") EXIST : (%9.1f",current_freq,") MHz \033[0m");
  end
end

initial begin
  while(print_en)begin
    wait( (freq_keep ==1) & lck );
    $display("\033[1;34m CRU_MON: (%7.6f",$realtime," ns) CLK(%-50s",NAME,") PERIOD CHANGE: (%6.1f",last_freq,")MHz ->  (%6.1f",current_freq,")MHz CHANGE_CNT:%3d",change_cnt," \033[0m");
    change_cnt <= change_cnt + 'b1;
    wait(freq_keep ==0);
  end
end

//clk gate test
bit gate_reg_ff1=1'b0;
bit gate_reg_ff2=1'b0;
bit gate_reg_ff3=1'b0;
bit [4:0]gate_reg_cnt=0;
always @(posedge clk_mon) begin
  gate_reg_ff1 <= gate_reg;
  gate_reg_ff2 <= gate_reg_ff1;
  gate_reg_ff3 <= gate_reg_ff2;

  if(gate_reg_ff2 ^ gate_reg_ff3)begin
    gate_reg_cnt <= 'b1;
  end
  else if(gate_reg_cnt == GDLY_CHK)begin
    gate_reg_cnt <= 'b0;
  end
  else if(gate_reg_cnt != 'b0)begin
    gate_reg_cnt <=gate_reg_cnt+'b1;
  end

  if(lck & (gate_reg_cnt == GDLY_CHK-1) )begin
    if(   gate_reg_ff2  &&   lost ) $display("\033[1;34m CRU_MON: (%7.6f",$realtime," ns) CLK(%-60s",NAME,") 1st Assert  Gate Check PASS \033[0m");//BLUE
    if(   gate_reg_ff2  && (~lost)) $display("\033[1;31m CRU_MON: (%7.6f",$realtime," ns) CLK(%-60s",NAME,") 1st Assert  Gate Check FAIL \033[0m");//RED
    if( (~gate_reg_ff2) && (~lost)) $display("\033[1;34m CRU_MON: (%7.6f",$realtime," ns) CLK(%-60s",NAME,") 2nd Release Gate Check PASS \033[0m");//BLUE
    if( (~gate_reg_ff2) && ( lost)) $display("\033[1;31m CRU_MON: (%7.6f",$realtime," ns) CLK(%-60s",NAME,") 2nd Release Gate Check FAIL \033[0m");//RED
  end
end

//rstn test
bit rst_reg_ff1=1'b0;
bit rst_reg_ff2=1'b0;
bit rst_reg_ff3=1'b0;
bit [4:0]rst_reg_cnt = 'b0;
bit      rst_stage   =1'b0;
bit      rst_chiprst_chk_en  =1'b1;

always @(posedge clk_mon) begin
  if ( (!lck) && (!rstn) )
    rst_stage = 1'b1; 

  if ( lck &&  rst_chiprst_chk_en)
    if( (rst_stage = 1'b1) & rstn )begin
      $display("\033[1;34m CRU_MON: (%7.6f",$realtime," ns) CLK(%-60s",NAME,") 0st Chip Reset Auto Release Check PASS \033[0m");//BLUE
      rst_chiprst_chk_en = 1'b0;
    end
    else begin
      $display("\033[1;31m CRU_MON: (%7.6f",$realtime," ns) CLK(%-60s",NAME,") 0st Chip Reset Auto Release Check FAIL \033[0m");//RED
      rst_chiprst_chk_en = 1'b0;
    end
end

always @(posedge clk_mon) begin
  //start
  rst_reg_ff1 <= rst_reg;
  rst_reg_ff2 <= rst_reg_ff1;
  rst_reg_ff3 <= rst_reg_ff2;

  if(rst_reg_ff2 ^ rst_reg_ff3)begin
    rst_reg_cnt <='b1;
  end
  else if(rst_reg_cnt == RDLY_CHK)begin
    rst_reg_cnt <= 'b0;
  end
  else if(rst_reg_cnt != 'b0)begin
    rst_reg_cnt <=rst_reg_cnt+'b1;
  end

  //check
  if(rst_reg_cnt == RDLY_CHK-1)begin
    if(   rst_reg_ff2  && (~rstn)) $display("\033[1;34m CRU_MON: (%7.6f",$realtime," ns) CLK(%-60s",NAME,") 1st Assert  RST Check PASS \033[0m");//BLUE
    if(   rst_reg_ff2  && ( rstn)) $display("\033[1;31m CRU_MON: (%7.6f",$realtime," ns) CLK(%-60s",NAME,") 1st Assert  RST Check FAIL \033[0m");//RED
    if( (~rst_reg_ff2) && ( rstn)) $display("\033[1;34m CRU_MON: (%7.6f",$realtime," ns) CLK(%-60s",NAME,") 2nd Release RST Check PASS \033[0m");//BLUE
    if( (~rst_reg_ff2) && (~rstn)) $display("\033[1;31m CRU_MON: (%7.6f",$realtime," ns) CLK(%-60s",NAME,") 2nd Release RST Check FAIL \033[0m");//RED
  end
end

//sel and div test
real expected_period          ; 
bit [12:0] expected_freq=0    ;
bit [12:0] freq_sub           ; assign freq_sub = current_freq > expected_freq ? (current_freq - expected_freq) : (expected_freq - current_freq);
always @(posedge clk_mon)begin
  case(sel_reg)
  2'b00   : expected_freq <= FREQ0 / (div_reg + 'd1);
  2'b01   : expected_freq <= FREQ1 / (div_reg + 'd1);
  2'b10   : expected_freq <= FREQ2 / (div_reg + 'd1);
  2'b11   : expected_freq <= FREQ3 / (div_reg + 'd1);
  default : expected_freq <= 1111.0 ;// 0     ;
  endcase
  expected_period  <= 1000.0 / expected_freq ;
end

bit efreq_en ; assign efreq_en = (expected_freq != 0) && lck; 
bit freq_cmp_ok ; assign freq_cmp_ok  = freq_sub < 10; 
bit freq_cmp_pass='b0;
bit freq_cmp_fail='b0;
bit [16:0] sel_reg_ff1='b0;
bit [16:0] sel_reg_ff2='b0;
bit [16:0] sel_reg_ff3='b0;
bit [ 7:0] sel_reg_cnt=0;
bit [ 7:0] sel_chg_cnt='b0;
always @(posedge clk_mon) begin
  //padding 
  sel_reg_ff1 <= {sel_reg,div_reg};
  sel_reg_ff2 <= sel_reg_ff1;
  sel_reg_ff3 <= sel_reg_ff2;

  //start
  if(sel_reg_ff2 != sel_reg_ff3)begin
    sel_reg_cnt <='b1;
  end
  else if(sel_reg_cnt == SDLY_CHK)begin
    sel_reg_cnt <= 'b0;
  end
  else if(sel_reg_cnt != 0)begin
    sel_reg_cnt <= sel_reg_cnt + 'b1;
  end

  //check
  if((sel_reg_cnt == SDLY_CHK-1) && efreq_en )begin
    sel_chg_cnt = sel_chg_cnt + 'b1;
    if( freq_cmp_ok ) begin
      $display("\033[1;34m CRU_MON: (%7.6f",$realtime," ns) CLK(%-50s",NAME,") : Expected Freq: (%6.1f",1000.0/expected_period,")MHz -> Real Freq:  (%6.1f",current_freq,")MHz CHANGE_CNT:%2d",sel_chg_cnt," sel_reg=(%2d",sel_reg,") div_reg=(%2d",div_reg,") Check PASS \033[0m");//BLUE
      freq_cmp_pass <= 1'b1;
      freq_cmp_fail <= 1'b0;
    end
    else  begin
      $display("\033[1;31m CRU_MON: (%7.6f",$realtime," ns) CLK(%-50s",NAME,") : Expected Freq: (%6.1f",1000.0/expected_period,")MHz -> Real Freq:  (%6.1f",current_freq,")MHz CHANGE_CNT:%2d",sel_chg_cnt," sel_reg=(%2d",sel_reg,") div_reg=(%2d",div_reg,") Check FAIL \033[0m");//RED
      freq_cmp_pass <= 1'b0;
      freq_cmp_fail <= 1'b1;
    end
  end
  else begin
      freq_cmp_pass <= 1'b0;
      freq_cmp_fail <= 1'b0;
  end
end

endmodule
